iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0
Mobile Development

從零開始以Flutter打造跨平台聊天APP系列 第 11

Day-11 在 Flutter 中使用 FutureBuilder 進行狀態管理

  • 分享至 

  • xImage
  •  

Generated from Stable Diffusion 3 Medium

在 Flutter App 中常常需要使用網路服務或者等待檔案系統回應,當我們使用這類服務時,整體的 UI 並不會阻塞。這些服務通常使用 async function 處理資料,並回傳 Future 物件。為了讓 UI 能在資料取得中與取得後切換,我們可以用 FutureBuilder 來建構畫面。FutureBuilder 在取得資料前及取得資料後會自動切換狀態,可以避免使用複雜的狀態管理。

範例程式傳送門: https://github.com/ksw2000/ironman-2024/tree/master/flutter-practice/future_builder

FutureBuilder 本身也是一個 StatefulWidget 。首先我們先了解如何建構它

class FutureBuilder<T> extends StatefulWidget {
  const FutureBuilder({
    super.key,
    required this.future,
    this.initialData,
    required this.builder,
  });

  final Future<T>? future;
  final AsyncWidgetBuilder<T> builder;
  final T? initialData;

  // ...
}

future 參數要代入一個 Future 物件,可以儲放向網路或檔案系統等異步請求的結果,為了方便演示,我們這裡設定延遲 2 秒後自動回傳一個字串:

Future.delayed(const Duration(seconds: 2), () => "SOME THING");

builder 的部分的一個函式參數,其中 context 的與 build() 中的 context 功能一樣,另一個 snapshot 用來代表 future 回傳值目前的狀態

Widget Function(BuildContext context, AsyncSnapshot<T> snapshot)

整體的寫法如下

FutureBuilder<String>(
  future: _fetch,
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    List<Widget> children;
    if (snapshot.hasData) {
      // 當 _fetch 回傳值後
      // 利用 snapshot.data 取得值
    } else if (snapshot.hasError) {
      // 當 _fetch 拋出錯誤 (throw error) 時
      // 利用 snaphost.error 取得值
    } else {
      // 初始狀態,通常我們會在這邊放一個轉圈圈 widget
    }
  }
)

有一點要注意的是:future 內的 Future 物件,必需在 build 之前被確定。也就是說我們不可以在 build() 才取得 Future 物件。如果每次 future 都和 FutureBuilder 同時建構,那麼當每次 FutureBuilder 的上層 Widget 被重建時,異步處理也會重新啟動。

// 錯誤範例
class UserPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var _future = ...
    return Scaffold(
        //...
        FutureBuilder(
          future: _future,
          builder: (context, snapshot) {
            // ...
          }
        )

我們可以利用上方的模版來設計畫面。延續昨天的進度,當我們登入後,也許還會需要向伺服器請求資料,因此我們可以在 UserPage 中,加入 FutuerBuilder

class UserPage extends StatelessWidget {
  UserPage({super.key});

  final Future<String> _fetch =
      Future.delayed(const Duration(seconds: 2), () => "SOME THING");

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: const Text('User Page'),
        ),
        body: Center(
            child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 20),
                child: FutureBuilder(
                    future: _fetch,
                    builder: (context, snapshot) {
                      if (snapshot.hasData) {
                        return Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: <Widget>[
                            Text(
                                'Hello ${UserDataLayer.of(context).user?.name}'),
                            const SizedBox(height: 10),
                            Text('${snapshot.data}')
                          ],
                        );
                      } else if (snapshot.hasError) {
                        return Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              const Icon(
                                Icons.error_outline,
                                color: Colors.red,
                                size: 60,
                              ),
                              Padding(
                                padding: const EdgeInsets.only(top: 16),
                                child: Text('Error: ${snapshot.error}'),
                              ),
                            ]);
                      }
                      return const CircularProgressIndicator();
                    }))));
  }
}

當 _fetch 正常回傳後執行的效果如下:

demo

我們也可以模擬當錯誤發生時:

final Future<String> _fetchError =
  Future.delayed(const Duration(seconds: 2), () {
    throw Exception('ERROR OCCUR');
  });

// ...
FutureBuilder(
  future: _fetchError

demo for error was thrown


後記:9/12 只寫了一點點,9/13努力補完🙌


上一篇
Day-10 Flutter路由管理
下一篇
Day-12 在 Flutter 中使用原生方法及第三方套件實現 http 連線
系列文
從零開始以Flutter打造跨平台聊天APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言